<script setup lang="tsx">
import { NButton, NTag } from 'naive-ui';
import { h, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useAuth } from '@/hooks/business/auth';
import { useTable } from '@/hooks/common/table';
import { $t } from '@/locales';
import type { XxlJobLog, XxlJobLogResult } from '@/service/api';
import { fetchLogDetailCat, fetchXxlJobLogList, killJob } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import SvgIcon from '@/components/custom/svg-icon.vue';
import { useBoolean } from '~/packages/hooks/src';
import JobLogSearch from './modules/job-log-search.vue';

const appStore = useAppStore();
const { hasAuth } = useAuth();

const { bool: resultMsgVisible, setTrue: openResultMsgModel } = useBoolean();
const handleMsgValue = ref('');
const handleMsgTitle = ref('');

const { bool: jobDetailVisible, setTrue: openJobDetailModel } = useBoolean();
const jobDetailValue = ref<string[]>([]);

const { bool: executorLogVisible, setTrue: openExecutorLogModel } = useBoolean();
const executorLogValue = ref('');
// const executorLogRunningValue = ref('');

const logRun = ref();

const jobGroup = ref<number>(-1);
const jobId = ref<number>(-1);

const { columns, data, loading, mobilePagination, getDataByPage, searchParams, resetSearchParams } = useTable({
  apiFn: fetchXxlJobLogList,
  showTotal: true,
  apiParams: {
    current: 1,
    size: 10,
    jobGroup: 0,
    jobId: 0,
    logStatus: -1,
    filterTime: null
  },
  columns: () => [
    {
      key: 'id',
      title: $t('page.base.xxl.log.id'),
      align: 'center'
    },
    {
      key: 'jobId',
      title: $t('page.base.xxl.log.jobId'),
      align: 'center',
      render: row => {
        if (!row.jobId) {
          return null;
        }
        return h(
          NButton,
          {
            type: 'info',
            dashed: true,
            size: 'small',
            onClick: () => jobDetailModel(row)
          },
          {
            default: () => row.jobId
          }
        );
      }
    },
    {
      key: 'triggerTime',
      title: $t('page.base.xxl.log.triggerTime'),
      align: 'center'
    },
    {
      key: 'triggerCode',
      title: $t('page.base.xxl.log.triggerCode'),
      align: 'center',
      render: row => {
        if (row.triggerCode === 200) {
          return <NTag type="success">{$t('common.success')}</NTag>;
        } else if (row.triggerCode === 500) {
          return <NTag type="error">{$t('common.fail')}</NTag>;
        }
        return '';
      }
    },
    {
      key: 'triggerMsg',
      title: $t('page.base.xxl.log.triggerMsg'),
      align: 'center',
      render: row => {
        if (!row.triggerMsg) {
          return null;
        }
        return h(
          NButton,
          {
            type: 'info',
            dashed: true,
            size: 'small',
            onClick: () => resultMsgModel(row.triggerMsg, 'triggerMsg')
          },
          {
            default: () => $t('common.look')
          }
        );
      }
    },
    {
      key: 'handleTime',
      title: $t('page.base.xxl.log.handleTime'),
      align: 'center'
    },
    {
      key: 'handleCode',
      title: $t('page.base.xxl.log.handleCode'),
      align: 'center',
      render: row => {
        if (row.handleCode === 200) {
          return <NTag type="success">{$t('common.success')}</NTag>;
        } else if (row.handleCode === 500) {
          return <NTag type="error">{$t('common.fail')}</NTag>;
        }
        return '';
      }
    },
    {
      key: 'handleMsg',
      title: $t('page.base.xxl.log.handleMsg'),
      align: 'center',
      render: row => {
        let showValue = row.handleMsg;
        if (!row.handleMsg) {
          return $t('common.null');
        }
        showValue = `<span style="color: red;">${showValue}</span>`;

        return h(
          NButton,
          {
            type: 'info',
            dashed: true,
            size: 'small',
            onClick: () => resultMsgModel(showValue, 'handleMsg')
          },
          {
            default: () => $t('common.look')
          }
        );
      }
    },
    {
      key: 'operate',
      title: $t('common.operate'),
      align: 'center',
      fixed: 'right',
      width: 250,
      render: row => (
        <div class="flex-center gap-8px">
          <div v-show={hasAuth('system:job-log:query') && row.triggerCode === 200}>
            <NButton type="primary" ghost size="small" onClick={() => executorLogModel(row)}>
              {{
                default: () => $t('page.base.xxl.log.executorLog'),
                icon: () => h(SvgIcon, { icon: 'ant-design:file-protect-outlined' })
              }}
            </NButton>
          </div>
          <div v-show={hasAuth('system:job-log:query') && row.handleCode !== 200 && row.handleTime === null}>
            <NButton type="primary" ghost size="small" onClick={() => handleKillJob(row)}>
              {{
                default: () => $t('page.base.xxl.log.killJob'),
                icon: () => h(SvgIcon, { icon: 'ant-design:file-protect-outlined' })
              }}
            </NButton>
          </div>
        </div>
      )
    }
  ]
});

function resultMsgModel(resultMsg: string, type: string) {
  handleMsgValue.value = resultMsg;
  if (type === 'triggerMsg') {
    handleMsgTitle.value = `${$t('page.base.xxl.log.triggerLog')}`;
  }
  if (type === 'handleMsg') {
    handleMsgTitle.value = `${$t('page.base.xxl.log.handleLog')}`;
  }
  openResultMsgModel();
}
function jobDetailModel(row: XxlJobLog) {
  jobDetailValue.value[0] = row.executorAddress;
  jobDetailValue.value[1] = row.executorHandler;
  jobDetailValue.value[2] = row.executorParam;
  openJobDetailModel();
}

/** 查看日志 */
let pullFailCount = 0;
let fromLineNum = 1;
async function executorLogModel(row: XxlJobLog) {
  executorLogValue.value = '';
  pullFailCount = 0;
  fromLineNum = 1;
  // trigger fail, end
  if (row.triggerCode !== 200) {
    logRunStop(`<span style="color: red;">${$t('page.base.xxl.log.joblogRollingLogTriggerFail')}</span>`);
    openExecutorLogModel();
    return;
  }

  // pullFailCount, max=20
  pullLog(row.id, row.handleCode);

  openExecutorLogModel();

  if (logRun.value !== null || logRun.value !== '') {
    // 判断计时器是否为空
    clearInterval(logRun.value);
    logRun.value = '';
  }

  logRun.value = setInterval(() => {
    pullLog(row.id, row.handleCode);
  }, 3000);
}
function pullLog(jobLogId: number, handleCode: number) {
  // pullFailCount, max=20
  pullFailCount += 1;
  if (pullFailCount + 1 > 20) {
    logRunStop(`<span style="color: red;">${$t('page.base.xxl.log.joblogRollingLogFailOften')}</span>`);
    return;
  }
  fetchLogDetailCat(jobLogId, fromLineNum).then(response => {
    const res = response.data;
    if (res !== null) {
      if (fromLineNum !== res.fromLineNum) {
        executorLogValue.value = 'pullLog fromLineNum not match';
        return;
      }
      if (fromLineNum > res.toLineNum) {
        executorLogValue.value = 'pullLog already line-end';

        // valid end
        if (res.isEnd) {
          logRunStop('<br><span style="color: green;">[Rolling Log Finish]</span>');
          return;
        }
        return;
      }
      // append content
      fromLineNum = res.toLineNum + 1;
      executorLogValue.value += (res as XxlJobLogResult).logContent;
      // handler already callback, end
      if (handleCode > 0) {
        logRunStop('<br><span style="color: green;">[Load Log Finish]</span>');
      }
    }
  });
}
function logRunStop(content: string) {
  clearInterval(logRun.value);
  logRun.value = '';
  executorLogValue.value += content;
}
function closeExecutorLogModal() {
  clearInterval(logRun.value);
  logRun.value = '';
}

/**
 * 停止任务
 *
 * @param row
 */
async function handleKillJob(row: XxlJobLog) {
  window.$dialog?.warning({
    title: $t('page.base.xxl.log.killJob'),
    style: 'width: 20%;',
    content: $t('page.base.xxl.log.killJobAffirm'),
    positiveText: $t('common.confirm'),
    negativeText: $t('common.cancel'),
    onPositiveClick: async () => {
      await killJob(row.id);
      window.$message?.success($t('page.base.xxl.log.killJobSuccess'));
      getDataByPage();
    },
    onNegativeClick: () => {}
  });
}

onMounted(async () => {
  jobGroup.value = -1;
  jobId.value = -1;
  const route = useRoute();
  const { query } = route;
  if (query.jobGroup !== undefined && query.jobGroup !== null) {
    jobGroup.value = Number.parseInt(query.jobGroup.toString(), 10);
  }
  if (query.jobId !== undefined && query.jobId !== null) {
    jobId.value = Number.parseInt(query.jobId.toString(), 10);
  }
});
</script>

<template>
  <div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
    <JobLogSearch
      v-if="hasAuth('system:job-info:query')"
      v-model:model="searchParams"
      :job-group="jobGroup"
      :job-id="jobId"
      @reset="resetSearchParams"
      @search="getDataByPage"
    />
    <NCard :title="$t('page.base.xxl.log.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
      <NDataTable
        :columns="columns"
        :data="data"
        size="small"
        :flex-height="!appStore.isMobile"
        :scroll-x="2088"
        :loading="loading"
        :row-key="row => row.id"
        remote
        :pagination="mobilePagination"
        class="sm:h-full"
      />
    </NCard>

    <!-- 调度日志 -->
    <NModal v-model:show="resultMsgVisible" :title="handleMsgTitle" preset="card" class="w-200">
      <NScrollbar class="max-h-150">
        <!-- eslint-disable-next-line vue/no-v-html -->
        <div class="content content-card" v-html="handleMsgValue"></div>
      </NScrollbar>
    </NModal>

    <!-- 任务详情 -->
    <NModal v-model:show="jobDetailVisible" :title="$t('page.base.xxl.log.jobDetail')" preset="card" class="w-200">
      <NScrollbar class="max-h-150 pr-20px">
        <NDescriptions label-placement="left" :column="1" bordered label-style="width: 200px">
          <NDescriptionsItem :label="$t('page.base.xxl.log.executorAddress')">
            {{ jobDetailValue[0] }}
          </NDescriptionsItem>
          <NDescriptionsItem :label="$t('page.base.xxl.log.executorHandler')">
            {{ jobDetailValue[1] }}
          </NDescriptionsItem>
          <NDescriptionsItem :label="$t('page.base.xxl.log.executorParam')">
            {{ jobDetailValue[2] }}
          </NDescriptionsItem>
        </NDescriptions>
      </NScrollbar>
    </NModal>

    <!-- 执行日志 Console -->
    <NModal
      v-model:show="executorLogVisible"
      :title="$t('page.base.xxl.log.executorLog')"
      preset="card"
      :mask-closable="false"
      :close-on-esc="false"
      class="w-80%"
      @after-leave="closeExecutorLogModal"
    >
      <NScrollbar class="h-180 pr-20px">
        <!-- eslint-disable-next-line vue/no-v-html -->
        <div class="content content-card" v-html="executorLogValue"></div>
        <!-- eslint-disable-next-line vue/no-v-html -->
        <!-- <div class="content content-card" v-html="executorLogRunningValue"></div> -->
      </NScrollbar>
    </NModal>
  </div>
</template>

<style scoped>
.content {
  white-space: pre-wrap; /* 用来控制容器的文本中带有空白符、换行符等的显示 */
}
.content-card {
  display: block;
  padding: 9.5px;
  margin: 0 0 10px;
  font-size: 13px;
  line-height: 1.42857143;
  color: #333;
  word-break: break-all;
  word-wrap: break-word;
  background-color: #f5f5f5;
  border: 1px solid #ccc;
  border-radius: 4px;
}
</style>
